SPB Git

spb/groupe-ka Public

Groupe KA — site du holding + KA ID (compte unique & SSO des 7 plateformes). Next.js 16, SQLite, Google & Apple login.

TypeScript 89.7% CSS 5.8% HTML 4.4%
6.0 KB · 158 lines tsx
Raw Blame History
1// Auteur : Simon-Pierre Boucher — contact@spboucher.ai2// Page d'identification d'un membre (cible du code QR de la carte) :3// grande photo, identité complète, badge de vérification. Le courriel et4// les coordonnées ne sont jamais exposés.5import type { Metadata } from "next";6import { db } from "@/lib/db";7import { roleLabel } from "@/lib/roles";89export const metadata: Metadata = {10  title: "Identification de membre | Groupe KA",11  description: "Identification d'un membre du Groupe KA par son KA-ID.",12  robots: { index: false },13};1415export const dynamic = "force-dynamic";1617function fmtDate(iso: string | null): string {18  if (!iso) return "—";19  const d = new Date(iso.includes("T") ? iso : iso.replace(" ", "T") + "Z");20  if (Number.isNaN(d.getTime())) return "—";21  return d22    .toLocaleDateString("fr-CA", { day: "numeric", month: "long", year: "numeric" })23    .replace(/^1 /, "1ᵉʳ ");24}2526export default async function IdentificationMembre({27  params,28}: {29  params: Promise<{ kaId: string }>;30}) {31  const { kaId } = await params;32  const clean = /^ka-\d{10}$/.test(kaId) ? kaId : null;33  const row = clean34    ? (db35        .prepare(36          "SELECT ka_id, name, avatar_url, google_sub, role, created_at, last_login FROM users WHERE ka_id = ?",37        )38        .get(clean) as39        | {40            ka_id: string;41            name: string;42            avatar_url: string | null;43            google_sub: string | null;44            role: string | null;45            created_at: string;46            last_login: string | null;47          }48        | undefined)49    : undefined;5051  if (!row) {52    return (53      <main className="mx-auto max-w-md px-4 pb-16 sm:px-6">54        <section className="pt-12 text-center sm:pt-14">55          <p className="kicker rise justify-center">Identification de membre</p>56          <h1 className="gk-display rise mt-4 text-[clamp(28px,5vw,40px)] leading-[1.02] font-bold tracking-[-0.035em] uppercase [animation-delay:0.08s]">57            Identifiant <span className="outline-txt">introuvable</span>58          </h1>59          <p className="rise mt-4 text-[14px] text-ink-2 [animation-delay:0.16s]">60            Aucun membre ne porte cet identifiant. Vérifiez le code, ou{" "}61            <a href="/connexion" className="underline underline-offset-4">62              créez votre KA ID63            </a>64            .65          </p>66        </section>67      </main>68    );69  }7071  const infos: [string, string][] = [72    ["Identifiant membre", row.ka_id],73    ...(roleLabel(row.role)74      ? ([["Statut", roleLabel(row.role)!]] as [string, string][])75      : []),76    ["Membre depuis", fmtDate(row.created_at)],77    ["Dernière connexion", fmtDate(row.last_login)],78    ["Compte", row.google_sub ? "Lié à Google" : "Courriel vérifié"],79    ["Valide sur", "Les 7 plateformes ·Ka"],80  ];8182  return (83    <main className="mx-auto max-w-md px-4 pb-16 sm:px-6">84      <section className="pt-10 text-center sm:pt-12">85        <p className="kicker rise justify-center">Identification de membre</p>8687        {/* ——— La grande photo ——— */}88        <div className="rise mt-7 flex justify-center [animation-delay:0.08s]">89          {row.avatar_url ? (90            // eslint-disable-next-line @next/next/no-img-element91            <img92              src={row.avatar_url}93              alt={`Photo de ${row.name}`}94              width={176}95              height={176}96              referrerPolicy="no-referrer"97              className="h-44 w-44 rounded-full border-[3px] border-ink object-cover shadow-[8px_8px_0_rgba(20,24,20,0.18)]"98            />99          ) : (100            <span className="gk-display flex h-44 w-44 items-center justify-center rounded-full border-[3px] border-ink bg-lime text-[64px] font-bold shadow-[8px_8px_0_rgba(20,24,20,0.18)]">101              {row.name.charAt(0).toUpperCase()}102            </span>103          )}104        </div>105106        <h1 className="gk-display rise mt-6 text-[clamp(28px,6vw,42px)] leading-[1.02] font-bold tracking-[-0.03em] [animation-delay:0.14s]">107          {row.name}108        </h1>109        <p className="rise mt-3 inline-flex items-center gap-2 rounded-full border-[1.5px] border-ink bg-lime px-4 py-[7px] text-[13px] font-bold [animation-delay:0.2s]">110          <span111            aria-hidden="true"112            className="flex h-[18px] w-[18px] items-center justify-center rounded-full bg-ink text-[11px] font-bold text-lime"113          >114115          </span>116          Membre vérifié du Groupe KA117        </p>118119        {/* ——— KA-ID ——— */}120        <div className="gk-card rise mt-7 !bg-ink p-6 text-paper [animation-delay:0.26s]">121          <p className="gk-mono text-[10px] tracking-[0.2em] uppercase text-[rgba(217,242,107,0.65)]">122            KA-ID123          </p>124          <p className="gk-mono mt-2 text-[clamp(22px,6vw,30px)] font-bold tracking-[0.14em] text-lime [text-shadow:0_0_24px_rgba(217,242,107,0.35)]">125            {row.ka_id}126          </p>127        </div>128129        {/* ——— Toutes les infos ——— */}130        <div className="gk-card rise mt-5 p-0 text-left [animation-delay:0.32s]">131          {infos.map(([label, value], i) => (132            <div133              key={label}134              className={`flex items-baseline justify-between gap-4 px-5 py-[13px] ${i < infos.length - 1 ? "border-b border-dashed border-[rgba(20,24,20,0.18)]" : ""}`}135            >136              <span className="klabel flex-none">{label}</span>137              <span138                className={`text-right text-[13.5px] font-semibold ${label === "Identifiant membre" ? "gk-mono text-green" : ""}`}139              >140                {value}141              </span>142            </div>143          ))}144        </div>145146        <p className="mt-5 text-[12.5px] text-ink-2">147          Cette page confirme l&apos;identité d&apos;un membre à partir du code148          QR de sa carte. Le courriel et les coordonnées ne sont jamais149          affichés.150        </p>151        <a href="/" className="btn btn-ghost mt-4">152          groupe-ka.com ↗153        </a>154      </section>155    </main>156  );157}158